﻿using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Linq.Expressions;

namespace Microsoft.EntityFrameworkCore
{
    /// <summary>
    /// Converts nullable bool values to and from their nullable numberic representation.
    /// </summary>
    /// <typeparam name="TNumber">numberic type</typeparam>
    public class NullableBoolToNumberConverter<TNumber> : ValueConverter<bool?, TNumber?>
        where TNumber : struct
    {
        public NullableBoolToNumberConverter(TNumber falseValue, TNumber trueValue,
            [CanBeNull] ConverterMappingHints mappingHints = null)
            : base(
                ToNumberNullable(falseValue, trueValue),
                ToBoolNullable(trueValue),
                mappingHints)
        {

        }


        private static Expression<Func<bool?, TNumber?>> ToNumberNullable(TNumber falseValue, TNumber trueValue)
        {
            CheckTypeSupported(
               typeof(TNumber).UnwrapNullableType(),
               typeof(NullableBoolToNumberConverter<TNumber>),
               typeof(int), typeof(short), typeof(long), typeof(sbyte),
               typeof(uint), typeof(ushort), typeof(ulong), typeof(byte),
               typeof(decimal), typeof(double), typeof(float));

            var modelType = typeof(bool?);
            var providerType = typeof(TNumber?);
            var param = Expression.Parameter(modelType, "value");
            var hasValueExpression = Expression.Property(param, modelType.GetProperty("HasValue"));
            var valueExpression = Expression.Property(param, modelType.GetProperty("Value"));
            var valueConditionExpression = Expression.Condition(Expression.IsTrue(valueExpression), Expression.Constant(trueValue), Expression.Constant(falseValue));
            var expresion = Expression.Condition(hasValueExpression, Expression.Convert(valueConditionExpression, providerType), Expression.Constant(null, providerType));

            return Expression.Lambda<Func<bool?, TNumber?>>(expresion, param);
        }

        private static Expression<Func<TNumber?, bool?>> ToBoolNullable(TNumber trueValue)
        {
            var modelType = typeof(bool?);
            var providerType = typeof(TNumber?);
            var param = Expression.Parameter(providerType, "value");
            var hasValueExpression = Expression.Property(param, providerType.GetProperty("HasValue"));
            var valueExpression = Expression.Property(param, providerType.GetProperty("Value"));
            var equalExpression = Expression.Equal(valueExpression, Expression.Constant(trueValue));
            var expresion = Expression.Condition(hasValueExpression, Expression.Convert(equalExpression, modelType), Expression.Constant(null, modelType));

            return Expression.Lambda<Func<TNumber?, bool?>>(expresion, param);

        }
    }
}
